﻿#if DEBUG
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Xyzzer.AsyncUI
{
    /// <summary>
    /// The class contains an BreakOnLoaded property that
    /// allows to debug a visual tree from the control
    /// it was applied to up to the visual tree root.
    /// </summary>
    /// <remarks>
    /// Just put a breakpoint in ControlLoaded()
    /// and set VisualTreeDebugger.BreakOnLoaded="True"
    /// on any FrameworkElement/Control.
    /// <para/>
    /// Set BreakOnLoaded if you want to put the breakpoint yourself.
    /// <para/>
    /// DebugVisualTree method has a local path variable
    /// that contains a list of all elements from the visual tree root
    /// up to the debugged control.
    /// <para/>
    /// Debug Output window contains common visual tree layout properties
    /// helpful in debugging layout issues.
    /// </remarks>
    public class VisualTreeDebugger
    {
        #region BreakOnLoaded
        /// <summary>
        /// BreakOnLoaded BreakOnLoaded Dependency Property
        /// </summary>
        public static readonly DependencyProperty BreakOnLoadedProperty =
            DependencyProperty.RegisterAttached(
            "BreakOnLoaded",
            typeof(bool),
            typeof(VisualTreeDebugger),
            new PropertyMetadata(false, OnBreakOnLoadedChanged));

        /// <summary>
        /// Gets the BreakOnLoaded property. This dependency property 
        /// indicates whether the debugger should BreakOnLoaded when control is loaded.
        /// </summary>
        public static bool GetBreakOnLoaded(DependencyObject d)
        {
            return (bool)d.GetValue(BreakOnLoadedProperty);
        }

        /// <summary>
        /// Sets the BreakOnLoaded property. This dependency property 
        /// indicates whether the debugger should BreakOnLoaded when control is loaded.
        /// </summary>
        public static void SetBreakOnLoaded(DependencyObject d, bool value)
        {
            d.SetValue(BreakOnLoadedProperty, value);
        }

        /// <summary>
        /// Handles changes to the BreakOnLoaded property.
        /// </summary>
        private static void OnBreakOnLoadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                ((FrameworkElement)d).Loaded += ControlLoaded;
            }
            else
            {
                ((FrameworkElement)d).Loaded -= ControlLoaded;
            }
        }
        #endregion

        #region BreakOnTap
        /// <summary>
        /// BreakOnTap Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty BreakOnTapProperty =
            DependencyProperty.RegisterAttached(
                "BreakOnTap",
                typeof(bool),
                typeof(VisualTreeDebugger),
                new PropertyMetadata(false, OnBreakOnTapChanged));

        /// <summary>
        /// Gets the BreakOnTap property. This dependency property 
        /// indicates whether the attached debugger should break when
        /// the FrameworkElement on which this property is set is tapped.
        /// </summary>
        public static bool GetBreakOnTap(DependencyObject d)
        {
            return (bool)d.GetValue(BreakOnTapProperty);
        }

        /// <summary>
        /// Sets the BreakOnTap property. This dependency property 
        /// indicates whether the attached debugger should break when
        /// the FrameworkElement on which this property is set is tapped.
        /// </summary>
        public static void SetBreakOnTap(DependencyObject d, bool value)
        {
            d.SetValue(BreakOnTapProperty, value);
        }

        /// <summary>
        /// Handles changes to the BreakOnTap property.
        /// </summary>
        private static void OnBreakOnTapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var frameworkElement = d as FrameworkElement;

            Debug.Assert(frameworkElement != null, "BreakOnTapProperty should only be set on FrameworkElements.");

            if ((bool)e.NewValue)
            {
                ((FrameworkElement)d).Tap += ControlTapped;
            }
            else
            {
                ((FrameworkElement)d).Tap -= ControlTapped;
            }
        }
        #endregion

        /// <summary>
        /// Occurs when the control this behavior is BreakOnLoaded to gets loaded.
        /// Put a breakpoint in here and set ControlDebugOnLoaded.BreakOnLoaded="True"
        /// on any control to debug its visual tree.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private static void ControlLoaded(object sender, RoutedEventArgs e)
        {
            var startElement = (DependencyObject)sender;
            DebugVisualTree(startElement);
        }

        static void ControlTapped(object sender, GestureEventArgs e)
        {
            var startElement = (DependencyObject)sender;
            DebugVisualTree(startElement);
        }

        #region DebugVisualTree()
        public static void DebugVisualTree(DependencyObject startElement)
        {
            var path = new List<DependencyObject>();
            var dob = startElement;

            while (dob != null)
            {
                path.Add(dob);
                dob = VisualTreeHelper.GetParent(dob);
            }

            for (int i = path.Count - 1; i >= 0; i--)
            {
                TraceDependencyObject(path[i], i);
            }

            // Put breakpoint here
            Debug.WriteLine(
                string.Format("Watch path[0] to path[{0}]",
                path.Count - 1));

            if (Debugger.IsAttached)
            {
                Debugger.Break();
            }
        }
        #endregion

        #region TraceDependencyObject()
        private static void TraceDependencyObject(DependencyObject dob, int i)
        {
            var frameworkElement = dob as FrameworkElement;

            if (frameworkElement == null)
            {
                Debug.WriteLine(
                    "path[{0}] is Dependency Object: {1}",
                    i,
                    dob.GetType());
            }
            else
            {
                var c = frameworkElement as Control;
                var cc = frameworkElement as ContentControl;
                var panel = frameworkElement as Panel;
                var parentGrid = frameworkElement.Parent as Grid;
                var image = frameworkElement as Image;

                Debug.WriteLine(
                    "path[{0}] is Control: {1}({2}):",
                    i,
                    frameworkElement.GetType(),
                    frameworkElement.Name ?? "<unnamed>");

                // Actual layout information
                Debug.WriteLine("\tActualWidth={0}\r\n\tActualHeight={1}", frameworkElement.ActualWidth, frameworkElement.ActualHeight);
                var pos = frameworkElement.TransformToVisual(Application.Current.RootVisual).Transform(new Point());
                var pos2 = frameworkElement.TransformToVisual(Application.Current.RootVisual).Transform(new Point(frameworkElement.ActualWidth, frameworkElement.ActualHeight));
                Debug.WriteLine("\tPosition - X={0}, Y={1}, Right={2}, Bottom={3}",
                    pos.X,
                    pos.Y,
                    pos2.X,
                    pos2.Y);

                // DataContext often turns out to be a surprise
                Debug.WriteLine("\tDataContext: {0} {1}", frameworkElement.DataContext, frameworkElement.DataContext != null ? "HashCode: " + frameworkElement.DataContext.GetHashCode() : "");

                // List common layout properties
                Debug.WriteLine("\tWidth={0}\r\n\tHeight={1}", frameworkElement.Width, frameworkElement.Height);
                Debug.WriteLine("\tHorizontalAlignment={0}\r\n\tVerticalAlignment={1}", frameworkElement.HorizontalAlignment, frameworkElement.VerticalAlignment);

                if (cc != null)
                {
                    Debug.WriteLine(
                        "\tHorizontalContentAlignment={0}\r\n\tVerticalContentAlignment={1}",
                        cc.HorizontalContentAlignment,
                        cc.VerticalContentAlignment);
                }

                Debug.WriteLine("\tMargins={0}", frameworkElement.Margin);

                if (cc != null)
                {
                    Debug.WriteLine("\tPadding={0}", cc.Padding);
                }

                if (image != null)
                {
                    Debug.WriteLine(
                        "\tImage.Source={0}",
                        image.Source);
                    var bi = image.Source as BitmapImage;

                    if (bi != null)
                    {
                        Debug.WriteLine(
                            "\tImage.Source/BitmapImage.UriSource={0}",
                            bi.UriSource);
                        bi.ImageFailed += (s, e) =>
                        {
                            Debug.WriteLine(
                                "Error loading BitmapImage.Source:{0}\r\n{1}",
                                bi.UriSource,
                                e.ErrorException);
                            Debugger.Break();
                        };
                    }
                }

                if (panel != null)
                {
                    Debug.WriteLine("\tBackground={0}", panel.Background);
                }
                else if (c != null)
                {
                    Debug.WriteLine("\tBackground={0}", BrushToString(c.Background));
                    Debug.WriteLine("\tForeground={0}", BrushToString(c.Foreground));
                }

                if (parentGrid != null)
                {

                    var col = Grid.GetColumn(frameworkElement);
                    var row = Grid.GetRow(frameworkElement);

                    if (parentGrid.ColumnDefinitions.Count != 0 || col != 0)
                    {
                        Debug.Assert(
                            col < parentGrid.ColumnDefinitions.Count,
                            string.Format("Column {0} not defined on the parent Grid!", col));
                        Debug.WriteLine(
                            "\tColumn: {0} ({1})",
                            col,
                            parentGrid.ColumnDefinitions[col].Width);
                    }

                    if (parentGrid.RowDefinitions.Count != 0 || row != 0)
                    {
                        Debug.Assert(
                            row < parentGrid.RowDefinitions.Count,
                            string.Format("Row {0} not defined on the parent Grid!", row));
                        Debug.WriteLine(
                            "\tRow: {0} ({1})",
                            row,
                            parentGrid.RowDefinitions[row].Height);
                    }
                }

                if (frameworkElement.Parent is Canvas)
                {
                    var x = Canvas.GetLeft(frameworkElement);
                    var y = Canvas.GetTop(frameworkElement);
                    var zIndex = Canvas.GetZIndex(frameworkElement);

                    Debug.WriteLine("\tCanvas - X={0}, Y={1}, ZIndex={2}", x, y, zIndex);
                }
            }
        }
        #endregion

        #region BrushToString()
        private static string BrushToString(Brush brush)
        {
            if (brush == null)
                return "";

            var solidColorBrush = brush as SolidColorBrush;

            if (solidColorBrush != null)
            {
                return string.Format("SolidColorBrush: {0}", solidColorBrush.Color);
            }

            var imageBrush = brush as ImageBrush;

            if (imageBrush != null)
            {
                var bi = imageBrush.ImageSource as BitmapImage;

                if (bi != null)
                {
                    return string.Format(
                        "ImageBrush: {0}, UriSource: {1}",
                        bi,
                        bi.UriSource);
                }

                return string.Format("ImageBrush: {0}", imageBrush.ImageSource);
            }

            return brush.ToString();
        }
        #endregion
    }
}
#endif